<?php
namespace app;

final class App
{
    /**
     * 默认配置
     * @var array
     */
    private static $configs = [
        'debug' => true,
        'app_type' => 'web',//默认应用类型
        'runtime_path' => __DIR__ . '/runtime',//runtime目录
        'view_path' => __DIR__ . '/view',//view目录
    ];
    
    /**
     * 应用类型
     * @var array
     */
    private static $appTypes = ['web', 'cli'];
    
    private static $instance = null;
    
    private $urlBase;
    
    private $urlPath;
    
    private $urlQuery;
    
    private function __construct()
    {
        if (version_compare(PHP_VERSION, '5.5.0', '<'))
        {
            die("needs PHP 5.5.0 or higher.");
        }
        if (!defined('CONFIG_PATH'))
        {
            define('CONFIG_PATH', __DIR__ . '/config');
        }
        
        $configFile = CONFIG_PATH . '/app.php';
        if (!file_exists($configFile))
        {
            die(sprintf("config file: %s is not exists, please create it first.", $configFile));
        }
        
        self::setEnvironmentVariables();
        //先引入公共函数，配置文件中需要用到env()函数
        require __DIR__ . '/common/functions.php';
        
        //引入配置
        $configs = require $configFile;
        $configs = self::$configs = array_merge(self::$configs, $configs);
        if (!in_array($configs['app_type'], self::$appTypes))
        {
            throw new \RuntimeException(sprintf(
                "Invalid app_type: '%s' in config.app_type. It should be one of these: [%s].", 
                $configs['app_type'],
                implode(',', self::$appTypes)
            ));
        }
        
        //定义路径常量，保证跟PHP本身的一致不含右斜线
        defined('SRC_PATH') or define('SRC_PATH', __DIR__);
        defined('VIEW_PATH') or define('VIEW_PATH', rtrim($configs['view_path'], '/'));
        defined('RUNTIME_PATH') or define('RUNTIME_PATH', rtrim($configs['runtime_path'], '/'));
        ini_set('date.timezone', $configs['timezone'] ?: 'UTC');
        
        //设置错误与异常处理
        if ($configs['debug'])
        {
            ini_set('error_reporting', -1);
            ini_set('display_errors', 1);
            ini_set('display_startup_errors', 1);
            ini_set('log_errors', 0);
            ini_set('error_log', null);
        }
        else 
        {
            ini_set('error_reporting', -1);
            ini_set('display_errors', 0);
            ini_set('display_startup_errors', 0);
            ini_set('log_errors', 1);
            ini_set('error_log', RUNTIME_PATH . '/php_error_log');
        }
        
        set_error_handler(['app\exception\AppExceptionHandler', 'error'], E_ALL);
        set_exception_handler(['app\exception\AppExceptionHandler', 'exception']);
        register_shutdown_function(['app\exception\AppExceptionHandler', 'shutdown']);
        
    }
    
    private function __clone()
    {
        
    }
    
    /**
     * 创建应用入口
     * @param array $configs
     */
    public static function make()
    {
        if (!is_null(self::$instance))
        {
            return self::$instance;
        }
        return self::$instance =  new self;
    }
    
    public function __set($key, $value)
    {
        throw new \RuntimeException("property: $key is not set.");
    }

    /**
     * 获取app实例
     */
    public static function getInstance()
    {
        return self::$instance;
    }
    
    /**
     * 获取配置
     * @param string $key
     * @param mixed $default
     */
    public function config($key, $default = null)
    {
        if (isset(self::$configs[$key]))
        {
            return self::$configs[$key];
        }
        else
        {
            return $default;
        }
    }
    
    public function url($url)
    {
        return sprintf("%s/%s", $this->urlBase, trim($url, '/'));
    }
    
    /**
     * 解析当前请求，获得路由。
     * @throws \RuntimeException
     * @return string
     */
    private function parseRoutes()
    {
        if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        {
            $protocol = 'https';
        }
        else 
        {
            $protocol = 'http';
        }
        $parseUrlResult = parse_url($_SERVER['REQUEST_URI']);
        if (!empty($parseUrlResult['query']))
        {
            $urlQuery = $parseUrlResult['query'];
        }
        else
        {
            $urlQuery = '';
        }
        $urlPath= trim($parseUrlResult['path'], '/');
        $this->urlQuery = $urlQuery;
        $this->urlPath = $urlPath;
        $urlBase = $this->urlBase = sprintf("%s://%s", $protocol, $_SERVER['HTTP_HOST']);
        
        $uri = empty($urlPath) ? '/' : $urlPath;
        
        $routes = $this->config('routes', []);
        
        if (!isset($routes[$uri]))
        {
            throw new \RuntimeException("Cound not resolve URI: $uri.");
        }
        $route = $routes[$uri];
        if (empty($route[1]))
        {
            $route[1] = 'GET';
        }
        
        $allowMethods = array_change_key_case(array_flip((array)$route[1]), CASE_UPPER);
        $method = $_SERVER['REQUEST_METHOD'];
        
        if (!isset($allowMethods[$method]))
        {
            throw new \RuntimeException(sprintf("method: '%s' not match route: %s", $method, print_r($route, true)));
        }
        
        return $route;
        
    }
    
    public function handle()
    {
        $route = $this->parseRoutes();
        $controllerAction = explode('::', $route[0], 2);
        $controller = $controllerAction[0];
        if (!class_exists($controller))
        {
            throw new \RuntimeException("Controller: $controller not exists.");
        }
        $controllerObj = new $controller;
        $action = $controllerAction[1];
        if (!is_callable([$controllerObj, $action]))
        {
            throw new \RuntimeException("Action: $action not exists in Controller: $controller.");
        }
        ob_start();
        $result = call_user_func([$controllerObj, $action]);
        $content = ob_get_clean();
        if (!empty($content))
        {
            die($content);
        }
        else 
        {
            die($result);
        }
    }
    
    private function setEnvironmentVariables()
    {
        $file = CONFIG_PATH . '/.env';
        if (!file_exists($file))
        {
            return;
        }
        if (!is_readable($file))
        {
            throw new \RuntimeException("env file: '$file' is not readable.");
        }
        $autoDetect = ini_get('auto_detect_line_endings');
        ini_set('auto_detect_line_endings', true);
        $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        foreach ($lines as $line)
        {
            $line = trim($line);
            if (strpos($line, '#') === 0 || strpos($line, '=') === false)
            {
                // #开头或无'='，跳过
                continue;
            }
            list($name, $value) = array_map('trim', explode('=', $line, 2));
            if (!isset($_ENV[$name]))
            {
                //处理在值中添加引号，最好不添加
                $firstCharacter = substr($value, 0, 1);
                $lastCharacter = substr($value, -1, 1);
                if (($firstCharacter === "'" && $lastCharacter === "'") || ($lastCharacter === '"' && $lastCharacter === '"'))
                {
                    $value = substr($value, 1, -1);
                }
                $_ENV[$name] = $value;
                putenv("{$name}={$value}");
            }
        }
        ini_set('auto_detect_line_endings', $autoDetect);
    }
    
}